package com.wmx.yuanyuan.util;


import cn.edu.hfut.dmic.webcollector.model.CrawlDatum;
import cn.edu.hfut.dmic.webcollector.model.CrawlDatums;
import cn.edu.hfut.dmic.webcollector.model.Page;
import cn.edu.hfut.dmic.webcollector.plugin.ram.RamCrawler;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.net.URLEncoder;

/**
 * 必应搜索
 *
 * @author wangMaoXiong
 */
@SuppressWarnings("all")
public class BingCrawler extends RamCrawler {

    public static final ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();

    /**
     * 必应搜索
     *
     * @param keyword    ：搜索的关键字
     * @param maxPageNum ：抓取的最大页码，大于等于 1
     * @throws Exception
     */
    public BingCrawler(String keyword, int maxPageNum) throws Exception {
        for (int pageNum = 1; pageNum <= maxPageNum; pageNum++) {
            String url = createBingUrl(keyword, pageNum);
//            String url = "https://wangmaoxiong.blog.csdn.net/?orderby=ViewCount";
            /**
             * cn.edu.hfut.dmic.webcollector.model.CrawlDatum：爬取任务的数据结构
             * 在WebCollector 2.5之后，可以为每个CrawlDatum添加附加信息metaData
             * 附加信息并不是为了持久化数据，而是为了能够更好地定制爬取任务
             * 在visit方法中，可以通过page.getMetaData()方法来访问CrawlDatum中的metaData
             */
            CrawlDatum datum = new CrawlDatum(url)
                    .type("searchEngine")
                    .meta("keyword", keyword)
                    .meta("pageNum", pageNum)
                    .meta("depth", 1);
            addSeed(datum);//添加种子任务
        }
    }

    /**
     * 根据关键词和页号拼接 Bing 搜索的 url 地址，如 https://cn.bing.com/search?q=瓷器first=5
     * 必应搜索页面默认每页显示 10 条数据，search 后面接搜索的关键字。first 后面接结果数据索引，如 [1,10] 为第一页数据，[11,20] 第二页数据，以此类推
     *
     * @param keyword 关键词
     * @param pageNum 页号
     * @return 对应的URL
     * @throws Exception 异常
     */
    public static String createBingUrl(String keyword, int pageNum) throws Exception {
        int first = pageNum * 10 - 9;
        keyword = URLEncoder.encode(keyword, "utf-8");//url 地址进行 utf-8 编码
        return String.format("http://cn.bing.com/search?q=%s&first=%s", keyword, first);
    }

    /**
     * 任务爬取
     *
     * @param page 当前访问页面的信息
     * @param next 可以手工将希望后续采集的任务加到next中（会参与自动去重）
     */
    @Override
    public void visit(Page page, CrawlDatums next) {
        // 如果遇到301或者302，手动跳转（将任务加到next中）
        // 并且复制任务的meta
        if (page.code() == 301 || page.code() == 302) {
            next.addAndReturn(page.location()).meta(page.meta());
            return;
        }
        String keyword = page.meta("keyword");
        int pageNum = page.metaAsInt("pageNum");
        int depth = page.metaAsInt("depth");

        if (page.matchType("searchEngine")) {
            Elements results = page.select("li.b_algo>h2>a");//使用 jsoup 语法获取搜索结果的 a 标签元素
            System.out.println("成功抓取关键词[" + keyword + "]第" + pageNum + "页的搜索结果,共 [" + results.size() + "] 个。");
            for (int rank = 0; rank < results.size(); rank++) {
                Element result = results.get(rank);
                /*
                我们希望继续爬取每条搜索结果指向的网页，这里统称为外链。
                我们希望在访问外链时仍然能够知道外链处于搜索引擎的第几页、第几条，
                所以将页号和排序信息放入后续的 CrawlDatum 中，为了能够区分外链和
                搜索引擎结果页面，type 设置为 outlink，这里的值完全由用户定义，可以设置一个任意的值
                */
                String href = result.attr("abs:href");
                next.addAndReturn(href)
                        .type("outlink")
                        .meta("keyword", keyword)
                        .meta("pageNum", pageNum)
                        .meta("rank", rank);
            }
        } else if (page.matchType("outlink")) {
            int rank = page.metaAsInt("rank");
            String referer = page.meta("referer");//当前页面的来源地址，即上级 url 地址
            String current_url = page.url();
            System.out.println("抓取深度[" + depth + "]，" + "第[" + pageNum + "]页，第[" + (rank + 1) + "]个结果：" + current_url + "，标题：" + page.doc().title());
            ObjectNode objectNode = JsonNodeFactory.instance.objectNode();
            objectNode.put("depth", depth);
            objectNode.put("pageNum", pageNum);
            objectNode.put("title", page.doc().title());
            objectNode.put("url", current_url);
            arrayNode.add(objectNode);
        }
        /*
        在经典爬虫中，每个网页都有一个referer信息，表示当前网页的链接来源。
        例如我们首先访问新浪首页，然后从新浪首页中解析出了新的新闻链接，
        则这些网页的referer值都是新浪首页。WebCollector不直接保存referer值，
        但我们可以通过下面的方式，将referer信息保存在metaData中，达到同样的效果。
        经典爬虫中锚文本的存储也可以通过下面方式实现。
        在一些需求中，希望得到当前页面在遍历树中的深度，利用metaData很容易实现
        这个功能，在将CrawlDatum添加到next中时，将其depth设置为当前访问页面
        的depth+1即可。
         */
        next.meta("depth", depth + 1).meta("referer", page.url());
    }

    public static void main(String[] args) throws Exception {
        BingCrawler crawler = new BingCrawler("湖南陶瓷", 3);
        crawler.setThreads(1);
        crawler.start();
//
        System.out.println(System.getProperty("user.home"));
    }
}
